//! 此文件创建 history 路由

export const removeTrailingSlash = (path) => path.replace(/\/$/, ""); // 删除尾部斜线

/**
 * 获取完整路径
 * @param {*} base
 * @param {*} location
 * @returns
 */
export function createHref(base, location) {
  return base.replace(/^[^#]+#/, "#") + location; // 基础路径中将两个哈希之间字符贴换成一个 # 字符
}

export function normalizeBase(base) {
  if (!base) {
    base = "/";
  }
  if (base[0] !== "/" && base[0] !== "#") base = "/" + base; // 保证基础路径带前缀斜杠
  return removeTrailingSlash(base);
}

export function stripBase(pathname, base) {
  if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase()))
    return pathname;
  return pathname.slice(base.length) || "/";
}

// 计算当前滚动条位置
export const computeScrollPosition = () => ({
  left: window.pageXOffset,
  top: window.pageYOffset,
});

let createBaseLocation = () => location.protocol + "//" + location.host; // 创建基础地址

/**
 * 创建当前 location 对象
 * @param {*} base
 * @param {*} location
 * @returns
 */
function createCurrentLocation(base, location) {
  const { pathname, search, hash } = location;
  const hashPos = base.indexOf("#"); // 判断是否使用 hash 路由
  if (hashPos > -1) {
    let slicePos = hash.includes(base.slice(hashPos))
      ? base.slice(hashPos).length
      : 1;
    let pathFromHash = hash.slice(slicePos);
    if (pathFromHash[0] !== "/") pathFromHash = "/" + pathFromHash;
    return stripBase(pathFromHash, "");
  }
  const path = stripBase(pathname, base);
  return path + search + hash;
}

/**
 * 监听 history 变化
 * @param {*} base 基础路径
 * @param {*} historyState 当前状态
 * @param {*} currentLocation 当前地址
 * @param {*} replace
 * @returns
 */
function useHistoryListeners(base, historyState, currentLocation, replace) {
  let listeners = []; // 用于存放监听的回调函数
  let teardowns = []; // 存放取消监听函数
  let pauseState = null; // 暂停状态

  const popStateHandler = ({ state }) => {
    const to = createCurrentLocation(base, location);
    const from = currentLocation.value;
    const fromState = historyState.value;
    let delta = 0;
    // 判断是否有状态
    if (state) {
      currentLocation.value = to;
      historyState.value = state;
      if (pauseState && pauseState === from) {
        pauseState = null;
        return;
      }
      delta = fromState ? state.position - fromState.position : 0; // 前进或是后退步数
    } else {
      replace(to);
    }
    // 发布监听回调函数
    listeners.forEach((listener) => {
      listener(currentLocation.value, from, {
        delta,
        type: "pop",
        direction: delta ? (delta > 0 ? "forward" : "back") : "",
      });
    });
  };

  function pauseListeners() {
    pauseState = currentLocation.value;
  }

  function listen(callback) {
    listeners.push(callback); // 订阅用户监听回调函数
    const teardown = () => {
      const index = listeners.indexOf(callback); // 查找相同回调函数
      if (index > -1) listeners.splice(index, 1);
    };
    teardowns.push(teardown);
    return teardown; // 返回一个取消监听函数
  }

  function destroy() {
    for (const teardown of teardowns) teardown(); // 取消所有监听函数
    teardowns = [];
    window.removeEventListener("popstate", popStateHandler); // 取消前进后退监听
  }

  // 监听 popstate 状态发生改变(主要是浏览器上的前进后退按钮)
  window.addEventListener("popstate", popStateHandler);

  return {
    pauseListeners,
    listen,
    destroy,
  };
}

/**
 * 创建一个 history 状态
 * @param {*} back 回退地址
 * @param {*} current 当前地址
 * @param {*} forward 前进地址
 * @param {*} replaced 是否 replace 贴换地址
 * @param {*} computeScroll 是否计算滚动条
 * @returns
 */
function buildState(
  back,
  current,
  forward,
  replaced = false,
  computeScroll = false
) {
  return {
    back,
    current,
    forward,
    replaced,
    position: window.history.length, // 地址在栈中位置
    scroll: computeScroll ? computeScrollPosition() : null,
  };
}

/**
 * 获取含有 location, state, push, replace 属性对象
 * @param {*} base 基础路径
 * @returns
 */
function useHistoryStateNavigation(base) {
  const { history, location } = window;
  // 当前 location 对象
  const currentLocation = {
    value: createCurrentLocation(base, location),
  };
  // 当前 state 状态
  const historyState = { value: history.state };
  //! 第一次刷新是没有状态信息需要我们自己维护一个状态
  if (!historyState.value) {
    // 首次刷新使用 replace 替换地址得到 history 自身维护的状态
    changeLocation(
      currentLocation.value,
      {
        back: null,
        current: currentLocation.value,
        forward: null,
        position: history.length - 1,
        replaced: true,
        scroll: null,
      },
      true
    );
  }
  /**
   * 改变 location 方法
   * @param {*} to 跳转路径
   * @param {*} state 状态数据
   * @param {*} replace 是否贴换
   */
  function changeLocation(to, state, replace) {
    const hashIndex = base.indexOf("#"); // hash 位置
    // 最终改变的地址
    const url =
      hashIndex > -1
        ? (location.host ? base : base.slice(hashIndex)) + to
        : createBaseLocation() + base + to; // 非 hash 路由直接拼接
    try {
      //! 兼容低版本浏览器不让报错阻止代码运行但是也不让用
      history[replace ? "replaceState" : "pushState"](state, "", url); // 使用 history 中方法改变地址栏
      historyState.value = state;
    } catch (err) {
      console.log(err);
      location[replace ? "replace" : "assign"](url);
    }
  }
  /**
   * replace 贴换地址
   * @param {*} to 贴换地址
   * @param {*} data 状态信息
   */
  function replace(to, data) {
    // 合并状态
    const state = Object.assign(
      {},
      history.state, // history 中自身状态
      buildState(historyState.value.back, to, historyState.value.forward, true), // 创建新状态
      data,
      { position: historyState.value.position } // 滚动条位置信息
    );
    // 改变地址路径
    changeLocation(to, state, true);
    currentLocation.value = to;
  }
  /**
   * push 追加地址
   * @param {*} to 追加地址
   * @param {*} data 状态信息
   */
  function push(to, data) {
    // 当前状态
    const currentState = Object.assign({}, historyState.value, history.state, {
      forward: to,
      scroll: computeScrollPosition(), // 计算滚动条数据
    });
    // 假跳转(本质原地跳转)只是为了获取更新状态信息为后续监听状态变化准备
    changeLocation(currentState.current, currentState, true); // 使用 replace 方法先进行原地贴换地址一次
    // 真实需要跳转状态
    const state = Object.assign(
      {},
      buildState(currentLocation.value, to, null),
      { position: currentState.position + 1 }, // 栈位置增加
      data
    );
    // 改变地址路径
    changeLocation(to, state, false);
    currentLocation.value = to; // 改变当前 location 数据
  }
  // 返回对象包含 location, state, push, replace 属性
  return {
    location: currentLocation,
    state: historyState,
    push,
    replace,
  };
}

/**
 * 创建 history 路由
 * @param {*} base
 * @returns
 */
export function createWebHistory(base) {
  base = normalizeBase(base); // 处理基础路径

  const historyNavigation = useHistoryStateNavigation(base);

  const historyListeners = useHistoryListeners(
    base,
    historyNavigation.state,
    historyNavigation.location,
    historyNavigation.replace
  );

  function go(delta, triggerListeners = true) {
    if (!triggerListeners) historyListeners.pauseListeners();
    history.go(delta); // 调用 window.history 上 go 方法
  }

  // 合并成一个 hisotry 对象
  const routerHistory = Object.assign(
    {
      location: "",
      base,
      go,
      createHref: createHref.bind(null, base),
    },
    historyNavigation,
    historyListeners
  );

  // 进行代理处理方便用户取值操作 history.location ===> history.location.value
  Object.defineProperty(routerHistory, "location", {
    enumerable: true,
    get: () => historyNavigation.location.value,
  });

  Object.defineProperty(routerHistory, "state", {
    enumerable: true,
    get: () => historyNavigation.state.value,
  });

  return routerHistory;
}
